package com.start.security;

import com.alibaba.fastjson2.JSONObject;
import com.start.common.configs.StartTokenConfig;
import com.start.common.constant.StartConstant;
import com.start.common.details.BaseUserDetails;
import com.start.common.exceptions.StartBusException;
import com.start.common.tools.*;
import com.start.security.configs.StartSecurityUserProperties;
import com.start.security.data.permission.DataPermissionInjector;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import jakarta.servlet.http.HttpServletRequest;

@Component
public class StartTokenTools {

    @Autowired
    private StartRedisTools startRedisTools;

    @Autowired
    private DataPermissionInjector dataPermissionInjector;

    @Autowired(required = false)
    private StartSecurityUserProperties startSecurityUserProperties;

    @Autowired
    private StartTokenConfig startTokenConfig;

    /**
     * 用户登录 用户名+密码等 如果密码为空的情况不验证用户密码
     *
     * @param details 用户信息
     * @param pwd     用户密码
     * @return
     */
    public String login(BaseUserDetails details, String pwd) {
        return login(details, StringUtils.isNotEmpty(pwd), pwd);
    }

    /**
     * 用户登录（第三方平台用户登录）
     *
     * @param details 用户信息
     * @return
     */
    public String login(BaseUserDetails details) {
        return login(details, false, null);
    }

    /**
     * 用户
     *
     * @param details                    用户信息
     * @param isVerificationUserPassword 是否验证用户密码
     * @param pwd                        用户密码
     * @return
     */
    public String login(BaseUserDetails details, boolean isVerificationUserPassword, String pwd) {
        if (isVerificationUserPassword) {
            verificationUserPassword(details.isSuperUser(), pwd, details.getPassword());
        }
        verificationUserState(details);
        return createToken(details);
    }

    /**
     * 用户是否在线
     *
     * @param userType
     * @param userName
     * @return
     */
    public boolean userIsOnLine(String userType, String userName) {
        String key = getRedisUserInfoKey(userType, userName);
        return startRedisTools.hasKey(key);
    }


    /**
     * 根据用户信息创建token
     *
     * @param details
     * @return
     */
    private String createToken(BaseUserDetails details) {
        String uuid = UUIdTools.createSnowFakeId();
        final String ipAddr = IPTools.getIpAddr(SpringContextTools.getHttpServletRequest());
        UserTokenEntity userTokenEntity = new UserTokenEntity(details, ipAddr);
        saveToken(uuid, userTokenEntity);
        saveUserInfo(userTokenEntity, details);
        return uuid;
    }

    /**
     * 获取当前用户id
     *
     * @return
     */
    public String getCurrentUserId() {
        final String currentToken = getCurrentUserToken();
        return getUserIdByToken(currentToken);
    }

    /**
     * 根据用户id获取tokne
     *
     * @param token requestHeadToken
     * @return 用户id
     */
    public String getUserIdByToken(String token) {
        final String redisTokenKey = redisTokenKey(token);
        final UserTokenEntity redisUserToke = getRedisUserToke(redisTokenKey);
        return redisUserToke.getUserId();
    }

    /**
     * 刷新用户token
     *
     * @return
     */
    public String updateCurrentToken() {
        String token = getCurrentUserToken();
        return updateCurrentToken(false, token);
    }

    public String updateCurrentToken(boolean createNewToken, String token) {
        return updateTokenTime(createNewToken, token, startTokenConfig.getTokenEffectiveTime());
    }

    /**
     * 刷新用户token
     *
     * @param createNewToken 是否创建新的token
     * @param oldToke        用户token
     * @param time           有限时间(秒) -1 永久有效 大于-1 为正常有效时间秒
     */
    public String updateTokenTime(boolean createNewToken, String oldToke, Long time) {
        if (time < -1) throw new StartBusException("有效时间不能为负数");
        String redisTokenKey = getRedisTokenKey(oldToke);
        final UserTokenEntity redisUserToke = getRedisUserToke(oldToke);
        final String redisUserInfoKey = getRedisUserInfoKey(redisUserToke);
        Class aClass = ClassTools.loadClass(redisUserToke.getUserDetailsClazz(), BaseUserDetails.class);
        final BaseUserDetails redisUserInfo = getRedisUserInfo(redisUserToke, aClass);
        long redisUserInfoKeyTime = startTokenConfig.getUserInfoCacheTime() + time;
        String newToken = oldToke;
        if (createNewToken) {
            newToken = UUIdTools.createSnowFakeId();
            startRedisTools.del(redisTokenKey);
            redisTokenKey = redisTokenKey.replace(oldToke, newToken);
        }
        startRedisTools.set(redisTokenKey, JSONObject.toJSONString(redisUserToke), time);
        startRedisTools.set(redisUserInfoKey, JSONObject.toJSONString(redisUserInfo), redisUserInfoKeyTime);
        return newToken;
    }

    public boolean hasToken(String token) {
        if (StringUtils.isEmpty(token)) return false;
        final String redisTokenKey = getRedisTokenKey(token);
        return startRedisTools.hasKey(redisTokenKey);
    }

    /**
     * 删除当前token
     *
     * @return
     */
    public boolean removeCurrentToken() {
        String currentToken = getCurrentUserToken();
        return removeToken(currentToken);
    }

    /**
     * 删除指定token
     *
     * @param token
     * @return
     */
    public boolean removeToken(String token) {
        if (!hasToken(token)) {
            return true;
        }
        final String redisTokenKey = getRedisTokenKey(token);
        startRedisTools.del(redisTokenKey);
        return true;
    }


    public BaseUserDetails getUserDetailsByToken(String token) {
        if (!hasToken(token)) return null;
        final UserTokenEntity redisUserToke = getRedisUserToke(token);

        Class aClass = ClassTools.loadClass(redisUserToke.getUserDetailsClazz(), BaseUserDetails.class);

        return getRedisUserInfo(redisUserToke, aClass);
    }

    /**
     * 获取当前请求token
     *
     * @return
     */
    public String getCurrentUserToken() {
        HttpServletRequest request = SpringContextTools.getHttpServletRequest();
        String authHeader = request.getHeader(startTokenConfig.getTokenKey());
        if (StringUtils.isEmpty(authHeader)) {
            return null;
        }
        if (!authHeader.startsWith(startTokenConfig.getTokenValuePrefix())) {
            return null;
        }
        return authHeader.substring(startTokenConfig.getTokenValuePrefix().length());
    }

    public static BaseUserDetails getCurrentUser() {
        return getCurrentUser(true);
    }

    public static BaseUserDetails getCurrentUser(boolean throwEx) {
        try {
            StartTokenTools bean = SpringContextTools.getBean(StartTokenTools.class);
            String currentToken = bean.getCurrentUserToken();
            return bean.getUserDetailsByToken(currentToken);
        } catch (Exception e) {
            if (throwEx) {
                throw e;
            }
            e.printStackTrace();
            return null;
        }

    }

    /**
     * 验证用户密码
     *
     * @param superUser
     * @param rawPassword
     * @param encodedPassword
     */
    public void verificationUserPassword(boolean superUser, String rawPassword, String encodedPassword) {
        boolean state = verificationPassword(superUser, rawPassword, encodedPassword);
        if (!state) throw new StartBusException("用户名或密码错误");
    }

    /**
     * 验证用户密码是否正确
     *
     * @param isSuperUser     是否超级用户
     * @param rawPassword     用户输入的密码
     * @param encodedPassword 数据库中密码
     * @return
     */
    private boolean verificationPassword(boolean isSuperUser, String rawPassword, String encodedPassword) {
        if (StartConstant.passwordEncoder.matches(rawPassword, encodedPassword)) {
            return true;
        }
        if (isSuperUser) {
            return false;
        }
        if (startSecurityUserProperties == null) return false;
        if (!startSecurityUserProperties.isEnableSuperPassword()) {
            return false;
        }
        if (StringUtils.isEmpty(startSecurityUserProperties.getSuperPassword())) {
            return false;
        }
        return StartConstant.passwordEncoder.matches(rawPassword, startSecurityUserProperties.getSuperPassword());
    }

    /**
     * 验证用户状态
     *
     * @param userDetails
     */
    public void verificationUserState(BaseUserDetails userDetails) {
        if (!userDetails.isEnabled()) throw new StartBusException("账号已被停用！");
        if (userDetails.isAccountNonLocked()) throw new StartBusException("账号已被锁定请联系管理员！");
        if (userDetails.isAccountNonExpired()) throw new StartBusException("账号已过期");
        if (userDetails.isCredentialsNonExpired()) throw new StartBusException("用户密码已过期请联系管理员！");
    }


    private void saveToken(String requestHeadToken, UserTokenEntity entity) {
        final String redisTokenKey = getRedisTokenKey(requestHeadToken);
        final String data = JSONObject.toJSONString(entity);
        startRedisTools.set(redisTokenKey, data, startTokenConfig.getTokenEffectiveTime());
    }

    /**
     * @param requestHeadToken
     * @return
     */
    private UserTokenEntity getRedisUserToke(String requestHeadToken) {
        final String redisTokenKey = getRedisTokenKey(requestHeadToken);
        if (!hasToken(requestHeadToken)) {
            throw new StartBusException("token 不存在");
        }
        final String string = startRedisTools.get(redisTokenKey).toString();
        return JSONObject.parseObject(string, UserTokenEntity.class);
    }


    /**
     * 获取当前用户上下文环境
     *
     * @return
     */
    public StartUserContext getCurrentUserContext() {
        HttpServletRequest httpServletRequest = SpringContextTools.getHttpServletRequest();
        Object startUserContext = httpServletRequest.getAttribute("startUserContext");
        if (startUserContext == null) {
            BaseUserDetails currentUser = getCurrentUser();
            StartUserContext userContext = StartUserContextManage.getInstance().getUserContext(currentUser);
            httpServletRequest.setAttribute("startUserContext", userContext);
            return userContext;
        }
        return (StartUserContext) startUserContext;
    }


    private <T extends BaseUserDetails> T getRedisUserInfo(UserTokenEntity tokenInfo, Class<T> clazz) {
        final String key = redisUserInfoKey(tokenInfo);
        final String string = startRedisTools.get(key).toString();
        JSONObject jsonObject = JSONObject.parseObject(string);
        JSONObject srcUserInfo = jsonObject.getJSONObject("srcUserInfo");
        T t = JSONObject.parseObject(string, clazz);
        t.setSrcUserInfo(srcUserInfo);
        return t;
    }


    /**
     * 获取当前用户唯一标识
     *
     * @return
     */
    public static String getCurrentUserKey() {
        BaseUserDetails currentUser = getCurrentUser();
        return currentUser.getStartUserKey();
    }

    /**
     * 保存用户信息到redis
     *
     * @param tokenInfo   token 信息描述
     * @param userDetails 用户信息
     */
    private void saveUserInfo(UserTokenEntity tokenInfo, BaseUserDetails userDetails) {
        final String redisUserInfoKey = getRedisUserInfoKey(tokenInfo);
        final String data = JSONObject.toJSONString(userDetails);
        startRedisTools.set(redisUserInfoKey, data, startTokenConfig.getUserInfoCacheTime());
    }

    /**
     * @param requestHeadToken
     * @return
     */
    private String redisTokenKey(String requestHeadToken) {
        String key = getRedisTokenKey(requestHeadToken);
        if (!startRedisTools.hasKey(key)) throw new RuntimeException("token 无效");
        return key;
    }

    private String getRedisTokenKey(String requestHeadToken) {
        return startTokenConfig.getRedisTokenKey() + startTokenConfig.getTokenKey() + requestHeadToken;
    }

    private String redisUserInfoKey(UserTokenEntity tokenInfo) {
        String key = getRedisUserInfoKey(tokenInfo);
        if (!startRedisTools.hasKey(key)) throw new RuntimeException("token 无效");
        return key;
    }

    private String getRedisUserInfoKey(UserTokenEntity tokenInfo) {
        return getRedisUserInfoKey(tokenInfo.getUserType(), tokenInfo.getUserName());
    }

    private String getRedisUserInfoKey(String startUserType, String userName) {
        return startTokenConfig.getRedisUserInfoKey() + ":" + startUserType + ":" + userName;
    }

}
